﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;

public static class PayPalMethods
{

    private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    
    /*
	*   Purpose: 	Gets the access token from PayPal
	*   Inputs:     n/a
	*   Returns:    access token
	*
	*/
    public static string getAccessToken()
    {
        string serviceUrl = getServiceUrl("/v1/oauth2/token");

        string clientId = getClientId();
        string clientSecret = getClientSecret();

        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        var httpWebRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);

        httpWebRequest.Accept = "application/json";
        httpWebRequest.Headers["Authorization"] = "Basic " + System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(clientId + ":" + clientSecret));
        httpWebRequest.Headers["AcceptLanguage"] = "en_US";
        httpWebRequest.Headers["PayPal-Partner-Attribution-Id"] = System.Configuration.ConfigurationManager.AppSettings.Get("SBN_CODE");
        httpWebRequest.Method = "POST";

        string post = "grant_type=client_credentials";

        using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
        {
            streamWriter.Write(post);
            streamWriter.Flush();
            streamWriter.Close();
        }

        var result = "";
        string accessToken = "";

        var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
        using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
        {
            result = streamReader.ReadToEnd();
        }

        string jsonResponse = JValue.Parse(result).ToString(Formatting.Indented);

        log4net.Config.XmlConfigurator.Configure();
        log.Info(
            "Service URL: " + serviceUrl + Environment.NewLine +
            "Request: " + post + Environment.NewLine +
            "Response: " + Environment.NewLine + jsonResponse
        );

        JObject o = JObject.Parse(result);
        accessToken = (string)o["access_token"];

        return accessToken;
    }


    /*
	*   Purpose: 	Gets the PayPal approval URL to redirect the user to.
    *   
	*   Inputs:     access_token (The access token received from PayPal)
	*   Returns:    approval URL
	*/
    public static string getApprovalUrl(string accessToken, string jsonRequest)
    {
        string serviceUrl = getServiceUrl("/v1/payments/payment");

        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        var httpWebRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);
        
        httpWebRequest.Accept = "application/json";
        httpWebRequest.ContentType = "application/json";
        httpWebRequest.Headers["Authorization"] = "Bearer " + accessToken;
        httpWebRequest.Headers["PayPal-Partner-Attribution-Id"] = System.Configuration.ConfigurationManager.AppSettings.Get("SBN_CODE");
        httpWebRequest.Method = "POST";

        using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
        {
            streamWriter.Write(jsonRequest);
            streamWriter.Flush();
            streamWriter.Close();
        }

        var result = "";
        var approvalUrl = "";

        var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
        using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
        {
            result = streamReader.ReadToEnd();
            JObject o = JObject.Parse(result);

            // parse out the approval_url link
            foreach (var link in o["links"])
            {
                if ((string)link["rel"] == "approval_url")
                {
                    approvalUrl = (string)link["href"];
                }
            }
        }

        string jsonResponse = JValue.Parse(result).ToString(Formatting.Indented);

        log4net.Config.XmlConfigurator.Configure();
        log.Info(
            "Service URL: " + serviceUrl + Environment.NewLine +
            "Request: " + jsonRequest + Environment.NewLine +
            "Response: " + Environment.NewLine + jsonResponse
        );

        return approvalUrl;
    }


    /*
    *   Purpose: 	Executes the previously created payment for a given paymentID for a specific user's payer id.
    *   
    *   Inputs:     paymentID (The id of the previously created PayPal payment)
    *               payerID (The id of the user received from PayPal)
    *               transactionAmountArray (amount array if updating the payment amount)
    *   Returns:    Tuple (pair) containing 
    *                  - http statuscode (int)
    *                  - json response object of the executed payment (JObject)
    */                  
    public static Tuple<int, JObject> doPayment(string accessToken, string paymentID, string payerID, string jsonUpdate = null)
    {
        string serviceUrl = getServiceUrl("/v1/payments/payment/" + paymentID + "/execute");

        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        var httpWebRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);

        httpWebRequest.Accept = "application/json";
        httpWebRequest.ContentType = "application/json";
        httpWebRequest.Headers["Authorization"] = "Bearer " + accessToken;
        httpWebRequest.Headers["PayPal-Partner-Attribution-Id"] = System.Configuration.ConfigurationManager.AppSettings.Get("SBN_CODE");
        httpWebRequest.Method = "POST";

        string jsonRequest = "";

        JObject requestObject = null;
        
        if (string.IsNullOrEmpty(jsonUpdate) == true)
        {
            requestObject = new JObject(
                new JProperty(
                    "payer_id", payerID
                )
            );

            jsonRequest = requestObject.ToString();
        }
        // update shipping: include "transactions" object that only contains "amount" object
        else
        {
            JObject updateObject = JObject.Parse(jsonUpdate);

            requestObject = new JObject(
                new JProperty(
                    "payer_id", payerID
                ),
                new JProperty(
                    "transactions", new JArray(
                        new JObject(
                            new JProperty(
                                "amount", updateObject
                            )
                        )
                    )
                )
            );

            jsonRequest = requestObject.ToString();
        }

        using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
        {
            streamWriter.Write(jsonRequest);
            streamWriter.Flush();
            streamWriter.Close();
        }

        string result = "";
        int httpStatusCode = 0;

        try
        {
            var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
            httpStatusCode = (int)httpResponse.StatusCode;

            using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
            {
                result = streamReader.ReadToEnd();
            }
        }
        catch (WebException ex)
        {
            if (ex.Response is HttpWebResponse)
            {
                HttpStatusCode statusCode = ((HttpWebResponse)ex.Response).StatusCode;
                httpStatusCode = (int)statusCode;

                log.Info("Server returned HTTP " + (int)statusCode + " (" + statusCode.ToString() + ")");

                using (WebResponse wResponse = (HttpWebResponse)ex.Response)
                {
                    using (Stream data = wResponse.GetResponseStream())
                    {
                        result = new StreamReader(data).ReadToEnd();
                    }
                }
            }
        }
        catch (Exception ex)
        {
            log.Info(
                "Error: " + ex
            );
        }

        string jsonResponse = JValue.Parse(result).ToString(Formatting.Indented);

        log4net.Config.XmlConfigurator.Configure();
        log.Info(
            "Service URL: " + serviceUrl + Environment.NewLine +
            "Request: " + jsonRequest + Environment.NewLine +
            "Response: " + Environment.NewLine + jsonResponse
        );

        JObject o = JObject.Parse(result);

        Tuple<int, JObject> resultPair = new Tuple<int, JObject>(httpStatusCode, o);
        return resultPair;
    }


    /*
    *   Purpose: 	Look up a payment resource, to get details about payments that have not yet been completed
    *   
    *   Inputs:     paymentID (The id of the created payment)
    *   Returns:    json response object
    */
    public static JObject lookUpPaymentDetails(string accessToken, string paymentID)
    {
        string serviceUrl = getServiceUrl("/v1/payments/payment/" + paymentID);

        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        var httpWebRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);

        httpWebRequest.Accept = "application/json";
        httpWebRequest.Headers["Authorization"] = "Bearer " + accessToken;
        httpWebRequest.Headers["PayPal-Partner-Attribution-Id"] = System.Configuration.ConfigurationManager.AppSettings.Get("SBN_CODE");
        httpWebRequest.Method = "GET";

        //Get Response
        HttpWebResponse myHttpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();

        var result = "";

        var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
        using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
        {
            result = streamReader.ReadToEnd();
        }

        string jsonResponse = JValue.Parse(result).ToString(Formatting.Indented);

        log4net.Config.XmlConfigurator.Configure();
        log.Info(
            "Service URL: " + serviceUrl + Environment.NewLine +
            "Response: " + Environment.NewLine + jsonResponse
        );

        JObject o = JObject.Parse(result);

        return o;
    }


    /*
    *   Purpose: 	Create a csrf token used for better security
    *   Inputs:     n/a
    *   Returns:    csrf token (for use in forms and session)
    *
    */
    public static string getCsrfToken()
    {
        Random rnd = new Random();
        Byte[] b = new Byte[32];
        rnd.NextBytes(b);
        string csrfToken = BitConverter.ToString(b);
        csrfToken = csrfToken.Replace("-", "");
        return csrfToken;
    }

    public static string getServiceUrl(string pathService)
    {
        bool sandboxFlag = Boolean.Parse(System.Configuration.ConfigurationManager.AppSettings.Get("SANDBOX_FLAG"));

        string serviceUrl = sandboxFlag ?
            System.Configuration.ConfigurationManager.AppSettings.Get("SANDBOX_ENDPOINT") + pathService :
            System.Configuration.ConfigurationManager.AppSettings.Get("LIVE_ENDPOINT") + pathService;

        return  serviceUrl;
    }
    
    public static string getClientId()
    {
        bool sandboxFlag = Boolean.Parse(System.Configuration.ConfigurationManager.AppSettings.Get("SANDBOX_FLAG"));

        string clientId = sandboxFlag ?
                System.Configuration.ConfigurationManager.AppSettings.Get("SANDBOX_CLIENT_ID") :
                System.Configuration.ConfigurationManager.AppSettings.Get("LIVE_CLIENT_ID");

        return  clientId;
    }

    public static string getClientSecret()
    {
        bool sandboxFlag = Boolean.Parse(System.Configuration.ConfigurationManager.AppSettings.Get("SANDBOX_FLAG"));

        string clientSecret = sandboxFlag ?
                System.Configuration.ConfigurationManager.AppSettings.Get("SANDBOX_CLIENT_SECRET") :
                System.Configuration.ConfigurationManager.AppSettings.Get("LIVE_CLIENT_SECRET");

        return  clientSecret;
    }

}
